using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using OhioState.AssetManaged;

namespace OhioState.Graphics.Proxy
{
    [Serializable()]
    public class MaterialProxy : SharedAsset, IMaterial, System.ICloneable
    {
        public static MaterialProxy White = new MaterialProxy("White");

        static MaterialProxy()
        {
            AssetManager.Instance.Add(White);
        }

        public MaterialProxy(String name) : base(name)
        {
            _rasterOps = new RasterOpsProxy();
            _shaderProgram = ShaderProgram.SolidColor;
            _frontMaterials = new MaterialColors();
            _backMaterials = new MaterialColors();
            _currentColor = new OSUColor(OSUColor.White);
            _uvValues = new Dictionary<String, UniformVariableValues>();
            //debug
            _lightCollection = LightCollectionProxy.NoLights;
            //end of debug
            AssetManager.Instance.Add(this);
        }
        private IRasterOps _rasterOps;
        public IRasterOps Compositor
        {
            get { return _rasterOps; }
            set { _rasterOps = value; FireUpdateChangedEvent(this); }
        }
        //public void AddMaterialChangedEventHandler(IAssetManagedHandler e);
        //public void RemoveMaterialChangedEventHandler(IAssetManagedHandler e);

        private ShaderProgram _shaderProgram;
        public IShaderProgram Shader 
        {
            get { return _shaderProgram; }
            set
            {
                //
                // For shared objects, follow this sequence:
                //   1. rewire the notification for when the object
                //        this property points to changes.
                //   2. set the property.
                //   3. perform any internal updates.
                //   4. Implicit in step 3 would be changing this 
                //         object and notifying its subscribers.
                ShaderProgram shader = value as ShaderProgram;
                if (shader != null)
                {
                    IAssetManagedHandler changedEvent = new IAssetManagedHandler(shader_ChangedEvent);
                    this.SetPropertyEventHandling(_shaderProgram, shader, changedEvent);
                    _shaderProgram = shader;
                    shader_ChangedEvent(_shaderProgram);
                }
            }
        }
        private MaterialColors _frontMaterials;
        public MaterialColors FrontMaterial 
        { 
            get { return _frontMaterials; }
            set { _frontMaterials = value; FireUpdateChangedEvent(this); }
        }
        private MaterialColors _backMaterials;
        public MaterialColors BackMaterial
        {
            get { return _backMaterials; }
            set { _backMaterials = value; FireUpdateChangedEvent(this); }
        }
        private bool _applyLighting = false;
        public bool ApplyLighting
        {
            get { return _applyLighting; }
            set { _applyLighting = value; FireUpdateChangedEvent(this); }
        }
        private OSUColor _currentColor;
        public OSUColor CurrentColor
        {
            get { return _currentColor; }
            set { _currentColor = value; FireUpdateChangedEvent(this); }
        }
        //debug
        private LightCollectionProxy _lightCollection;
        public LightCollectionProxy LightCollection
        {
            get { return _lightCollection; }
            set { _lightCollection = value; FireUpdateChangedEvent(this); }
        }
        //end of debug
        public void MakeActive(IRenderPanel panel)
        {
        }
        public void Deactivate(IRenderPanel panel)
        {
        }
        protected void material_ChangedEvent(IAssetManaged obj)
        {
            FireUpdateChangedEvent(this);
        }
        public Object Clone()
        {
            MaterialProxy newMaterial = new MaterialProxy(String.Concat(this.Name," copy"));
            ICloneable clone = _rasterOps as ICloneable;
            if (clone != null)
            {
                newMaterial.Compositor = (IRasterOps)clone.Clone();
            }
            newMaterial.Shader = this.Shader;
            clone = _frontMaterials as ICloneable;
            if (clone != null)
            {
                newMaterial.FrontMaterial = (MaterialColors)clone.Clone();
            }
            clone = _backMaterials as ICloneable;
            if (clone != null)
            {
                newMaterial.BackMaterial = (MaterialColors)clone.Clone();
            }
            newMaterial.CurrentColor = new OSUColor(_currentColor);
            newMaterial.shader_ChangedEvent(this);
            return newMaterial;
        }
        /// <summary>
        /// Overrides shared object ToString method
        /// </summary>
        /// <returns>A string representing this object's name</returns>
        public override string ToString()
        {
            return this.Name;
        }
        protected Dictionary<String, UniformVariableValues> _uvValues;
        public Dictionary<String, UniformVariableValues> UniformVariableValues
        {
            get
            {
                shader_ChangedEvent(this);
                return _uvValues;
            }
        }
        protected void shader_ChangedEvent(IAssetManaged obj)
        {
            Dictionary<String, UniformVariableInfo> uv = ((ShaderProgram)Shader).GetUniformVariables();

            List<String> keysToRemove = new List<String>();
            foreach (KeyValuePair<String, UniformVariableValues> kvp in _uvValues)
            {
                //
                // Check for uniform variable names that were removed
                //
                if (!uv.ContainsKey(kvp.Key))
                {
                    keysToRemove.Add(kvp.Key);
                }
            }
            foreach (String key in keysToRemove)
            {
                _uvValues.Remove(key);
            }

            foreach (KeyValuePair<String, UniformVariableInfo> kvp in uv)
            {
                //
                // check if the uniform variable name already exists and whether its info has changed
                //
                if (kvp.Key != null)
                {
                    if (_uvValues.ContainsKey(kvp.Key))
                    {
                        _uvValues[kvp.Key].UVInfo = kvp.Value;
                    }
                    else
                    {
                        // add the new variable
                        UniformVariableValues uvv = new UniformVariableValues(kvp.Value);
                        uvv.AddChangedEventHandler( new IAssetManagedHandler(uvv_ChangedEvent));
                        _uvValues[kvp.Key] = uvv;
                    }
                }
            }

            // Inform interested parties that we have changed
            // This can not be called now, as it causes an infinite-loop of changes.
            // We need the UV-mapping to be performed or handled in the Proxy objects.
            //
            //FireUpdateChangedEvent(this);
        }
        private void uvv_ChangedEvent(IAssetManaged obj)
        {
            // Inform interested parties that we have changed
            FireUpdateChangedEvent(this);
        }

        #region File operations
        //
        // LoadFromFile - static function that loads a material from a file
        //  using binary deserialization.  Returns null if these is a problem.
        //
        public static MaterialProxy LoadFromFile(String filename)
        {
            MaterialProxy obj;

            Stream stream = File.Open(filename, FileMode.Open);
            BinaryFormatter formatter = new BinaryFormatter();

            try
            {
                obj = (MaterialProxy)formatter.Deserialize(stream);
            }
            catch
            {
                obj = null;
            }
            stream.Close();

            return obj;
        }

        //
        // SaveToFile - function that saves a material to a file
        //  using binary serialization.  This is not static and is simply called on the
        //  object that needs to be saved
        //
        public void SaveToFile(String filename)
        {
            //
            // Make a clone to save.  The marshalling done by remoting causes problems with 
            //  serialization.
            //
            MaterialProxy obj = (MaterialProxy)this.Clone();

            Stream stream = File.Open(filename, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, obj);
            stream.Close();
        }
        public override void ReaquireAssets()
        {
            //
            // Use the Properties to ensure the old event handlers are removed and new
            //    ones are added. Particularly if we are setting an item to itself.
            //
            if (_shaderProgram != null)
            {
                IAssetManaged item = AssetManager.Instance.Find(_shaderProgram.GetType(), _shaderProgram.Name);
                if (item != _shaderProgram)
                {
                    this.Shader = item as ShaderProgram;
                }
            }
        }
        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            if (_shaderProgram != null)
            {
                IAssetManagedHandler changedEvent = new IAssetManagedHandler(material_ChangedEvent);
                _shaderProgram.AddChangedEventHandler(changedEvent);
            }
        }
        #endregion
    }
}